package com.smartboard.network.client;

import android.util.Log;

import com.smartboard.network.game.AbstractGame;
import com.smartboard.network.game.GameObject;
import com.smartboard.network.game.IPlayGame;
import com.smartboard.network.game.IRoom;
import com.smartboard.network.game.IUser;
import com.smartboard.network.socket.SocketClient;

import java.io.Serializable;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by cheas on 2018/8/13.
 */

public class GameClient extends AbstractGame {
    private static final String TAG = "GameClient";

    public static final String SERVER_IP = "120.24.75.84";

    private List<GameEventListener> mListeners;
    private SocketClient mSocket;
    private IUser mLoginUser;
    private List<IRoom> mRooms = new ArrayList<>();
    private int mChessType;
    private int mErrorCode;

    private static GameClient sGameClient;

    public static GameClient inst() {
        return sGameClient;
    }

    public static GameClient create(int chessType) {
        if(sGameClient == null)
            sGameClient = new GameClient(chessType);
        return sGameClient;
    }

    private GameClient(int chessType) {
        mListeners = new ArrayList<>();
        mSocket = new SocketClient(SERVER_IP, getPort(chessType));
        mSocket.setOnReceiveListener(this);
        mChessType = chessType;
    }

    public IUser getUser() {
        return mLoginUser;
    }

    public int getErrorCode() {
        return mErrorCode;
    }

    public int getChessType() {
        return mChessType;
    }

    public List<IRoom> getRooms() {
        return mRooms;
    }

    public boolean connect() {
        return mSocket.open();
    }

    public void disconnect() {
        if(!mSocket.isClosed())
            mSocket.close();
    }

    public void registerEventListener(GameEventListener listener) {
        for(GameEventListener ln : mListeners) {
            if(ln == listener)
                return;
        }
        mListeners.add(listener);
    }

    public void unregisterEventListener(GameEventListener listener) {
        for(int i = 0; i < mListeners.size(); i++) {
            if(mListeners.get(i) == listener) {
                mListeners.remove(i);
                break;
            }
        }
    }

    private void send(int cmd, Serializable content) {
        GameObject gameData = new GameObject();
        gameData.cmd = cmd;
        gameData.type = CMD_TYPE_SEND;
        gameData.content = content;
        mSocket.send(gameData);
    }

    private Object sendSync(int cmd, Serializable content) {
        send(cmd, content);
        do {
            synchronized (mSyncObj) {
                try {
                    mSyncObj.wait(10000);
                    if(mRecvData == null)
                        return null;
                    if (cmd == mRecvData.cmd && mRecvData.type == CMD_TYPE_RESP)
                        return mRecvData.content;
                    continue;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        } while (true);
    }

    private void updateRoom(IRoom room) {
        if(mRooms == null)
            return;
        for(int i = 0; i < mRooms.size(); i++) {
            IRoom r = mRooms.get(i);
            if(r.equals(room)) {
                mRooms.remove(i);
                mRooms.add(i, room);
                return;
            }
        }
    }

    private void updateRoomUser(IUser user, int roomId) {
        if(mRooms == null)
            return;
        for(IRoom r : mRooms) {
            if(r.getId() == roomId) {
                for(int i = 0; i < r.getUsers().size(); i++) {
                    IUser u = r.getUsers().get(i);
                    if(u.equals(user)) {
                        r.getUsers().remove(i);
                        r.getUsers().add(i, user);
                        return;
                    }
                }
                return;
            }
        }
    }

    public IRoom getRoom(int roomId) {
        for(IRoom r : mRooms) {
            if(r.getId() == roomId)
                return r;
        }
        return null;
    }

    private void handleLogin(GameObject gameData) {
        IUser user = ((RespDataBase)gameData.content).user;
        for(GameEventListener l : mListeners) {
            l.onUserLogin(user);
        }
    }

    private void handleLogout(GameObject gameData) {
        IUser user = ((RespDataBase)gameData.content).user;
        for(GameEventListener l : mListeners) {
            l.onUserLogout(user);
        }
    }

    private void handlePlayMove(GameObject gameData) {
        RespPlayMoveData data = (RespPlayMoveData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onPlayMove(data.user, data.game, data.move);
        }
    }

    private void handleUndo(GameObject gameData) {
        RespGameData data = (RespGameData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onUndo(data.user, data.game);
        }
    }

    private void handleResign(GameObject gameData) {
        RespGameData data = (RespGameData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onResign(data.user, data.game);
        }
    }

    private void handleDraw(GameObject gameData) {
        RespGameData data = (RespGameData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onDraw(data.user, data.game);
        }
    }

    private void handleReady(GameObject gameData) {
        RespGameData data = (RespGameData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onReady(data.user, data.game);
        }
    }

    private void handleGameStart(GameObject gameData) {
        RespGameData data = (RespGameData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onGameStart(data.game);
        }
    }

    private void handleGameOver(GameObject gameData) {
        RespGameData data = (RespGameData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onGameOver(data.game);
        }
    }

    private void handleMessage(GameObject gameData) {
        RespMessageData data = (RespMessageData)gameData.content;
        for(GameEventListener l : mListeners) {
            l.onMessage(data.user, data.room, data.game, data.toUser, data.msg);
        }
    }

    private void handleRoomNumChanged(GameObject gameData) {
        RoomNumChangedData data = (RoomNumChangedData)gameData.content;
        mRooms = data.rooms;
        for(GameEventListener l : mListeners) {
            l.onRoomNumChanged(data.rooms);
        }
    }

    private void handleGameNumChanged(GameObject gameData) {
        GameNumChangedData data = (GameNumChangedData)gameData.content;
        updateRoom(data.room);
        for(GameEventListener l : mListeners) {
            l.onGameNumChanged(data.room);
        }
    }

    private void handleRoomUserChanged(GameObject gameData) {
        RoomUserChangedData data = (RoomUserChangedData)gameData.content;
        updateRoom(data.room);
        for(GameEventListener l : mListeners) {
            l.onRoomUserChanged(data.room, data.user, data.leave);
        }
    }

    private void handleGameUserChanged(GameObject gameData) {
        GameUserChangedData data = (GameUserChangedData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onGameUserChanged(data.game, data.user, data.leave);
        }
    }

    private void handleGameStateChanged(GameObject gameData) {
        GameStateChangedData data = (GameStateChangedData)gameData.content;
        updateRoom(data.game.getRoom());
        for(GameEventListener l : mListeners) {
            l.onGameStateChanged(data.game);
        }
    }

    private void handleUserInfoChanged(GameObject gameData) {
        UserInfoChangedData data = (UserInfoChangedData)gameData.content;
        if(data.user.getRoomId() != 0) {
            updateRoomUser(data.user, data.user.getRoomId());
        }
        for(GameEventListener l : mListeners) {
            l.onUserInfoChange(data.user);
        }
    }

    @Override
    protected void handleRespDate(GameObject data) {
        // 处理服务器响应数据
        RespDataBase respData = (RespDataBase)data.content;
        mErrorCode = respData.errorCode;
        if(respData.errorCode != ERROR_NONE) {
            for(GameEventListener l : mListeners) {
                l.onRespError(data.cmd, respData.errorCode);
            }
        }
    }

    @Override
    protected void handleReceiveData(Socket socket, GameObject gameData) {
        Log.d(TAG, "handleReceiveData() cmd:" + gameData.cmd);
        switch (gameData.cmd) {
            case CMD_LOGIN: {
                handleLogin(gameData);
                break;
            }
            case CMD_LOGOUT: {
                handleLogout(gameData);
                break;
            }
            case CMD_PLAY_MOVE: {
                handlePlayMove(gameData);
                break;
            }
            case CMD_UNDO: {
                handleUndo(gameData);
                break;
            }
            case CMD_RESIGN: {
                handleResign(gameData);
                break;
            }
            case CMD_DRAW: {
                handleDraw(gameData);
                break;
            }
            case CMD_READY: {
                handleReady(gameData);
                break;
            }
            case CMD_GAME_START: {
                handleGameStart(gameData);
                break;
            }
            case CMD_GAME_OVER: {
                handleGameOver(gameData);
                break;
            }
            case CMD_SEND_MESSAGE: {
                handleMessage(gameData);
                break;
            }
            case CMD_ROOM_NUM_CHANGED: {
                handleRoomNumChanged(gameData);
                break;
            }
            case CMD_GAME_NUM_CHANGED: {
                handleGameNumChanged(gameData);
                break;
            }
            case CMD_ROOM_USER_CHANGED: {
                handleRoomUserChanged(gameData);
                break;
            }
            case CMD_GAME_USER_CHANGED: {
                handleGameUserChanged(gameData);
                break;
            }
            case CMD_GAME_STATE_CHANGED: {
                handleGameStateChanged(gameData);
                break;
            }
            case CMD_USER_INFO_CHANGED: {
                handleUserInfoChanged(gameData);
                break;
            }
            default: {
                Log.d(TAG, "cmd not handled:" + gameData.cmd);
                break;
            }
        }
    }

    public int login(String userName, String password) {
        RespDataBase data = (RespDataBase)sendSync(CMD_LOGIN, new LoginData(userName, password, mChessType));
        if(data == null) {
            Log.d(TAG, "login fail, please check network, and try again!!");
            mErrorCode = ERROR_TIMEOUT;
            return ERROR_TIMEOUT;
        }

        if(data.user == null) {
            Log.d(TAG, "login fail, error code: " + data.errorCode);
            mErrorCode = data.errorCode;
            return data.errorCode;
        }

        mErrorCode = 0;
        mLoginUser = data.user;
        return 0;
    }

    public boolean hasLogin() {
        return (mLoginUser != null);
    }

    public void logout() {
        if(!hasLogin()) {
            Log.d(TAG, "logout fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_LOGOUT, new LogoutData(mLoginUser.getId()));
    }

    public IRoom createRoom(String roomName, String password) {
        if(!hasLogin()) {
            Log.d(TAG, "createRoom fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return null;
        }
        RespCreateRoomData data = (RespCreateRoomData)sendSync(CMD_CREATE_ROOM,
                new CreateRoomData(mLoginUser.getId(), roomName, password));
        if(data == null) {
            mErrorCode = ERROR_TIMEOUT;
            return null;
        }
        mErrorCode = data.errorCode;
        return data.room;
    }

    public void enterRoom(IRoom room, String password) {
        if(!hasLogin()) {
            Log.d(TAG, "enterRoom fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_ENTER_ROOM, new EnterRoomData(mLoginUser.getId(), room.getId(), password));
    }

    public void exitRoom(IRoom room) {
        if(!hasLogin()) {
            Log.d(TAG, "exitRoom fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_EXIT_ROOM, new ExitRoomData(mLoginUser.getId(), room.getId()));
    }

    public List<IRoom> fetchAllRooms() {
        if(!hasLogin()) {
            Log.d(TAG, "getRooms fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return null;
        }
        RespGetRoomsData data = (RespGetRoomsData)sendSync(CMD_GET_ROOMS, new GetRoomsData(mLoginUser.getId()));
        if(data == null) {
            mErrorCode = ERROR_TIMEOUT;
            return null;
        }

        mErrorCode = data.errorCode;
        return data.rooms;
    }

    public List<IPlayGame> createGames(IRoom room, int num, Object gameInfo) {
        if(!hasLogin()) {
            Log.d(TAG, "createGame fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return null;
        }
        RespCreateGameData data = (RespCreateGameData)sendSync(CMD_CREATE_GAME,
                new CreateGameData(mLoginUser.getId(), room.getId(), num, gameInfo));
        if(data == null) {
            mErrorCode = ERROR_TIMEOUT;
            return null;
        }
        mErrorCode = data.errorCode;
        return data.games;
    }

    public IPlayGame createGame(IRoom room, Object gameInfo) {
        List<IPlayGame> games = createGames(room, 1, gameInfo);
        if(games == null || games.size() == 0)
            return null;
        return games.get(0);
    }

    public void joinGame(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "joinGame fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_JOIN_GAME, new JoinGameData(mLoginUser.getId(), game.getId()));
    }

    public void quitGame(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "quitGame fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_QUIT_GAME, new QuitGameData(mLoginUser.getId(), game.getId()));
    }

    public void playMove(IPlayGame game, String move) {
        if(!hasLogin()) {
            Log.d(TAG, "playMove fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_PLAY_MOVE, new PlayMoveData(mLoginUser.getId(), game.getId(), move));
    }

    public void undoRequest(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "undoRequest fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        sendSync(CMD_UNDO, new UndoData(mLoginUser.getId(), game.getId(), USER_REQUEST, 0));
    }

    public void undoRespond(IPlayGame game, int resp) {
        if(!hasLogin()) {
            Log.d(TAG, "undoRespond fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_UNDO, new UndoData(mLoginUser.getId(), game.getId(), USER_RESP, resp));
    }

    public void resign(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "resign fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_RESIGN, new ResignData(mLoginUser.getId(), game.getId()));
    }

    public void drawRequest(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "drawRequest fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_DRAW, new DrawData(mLoginUser.getId(), game.getId(), USER_REQUEST, 0));
    }

    public void drawRespond(IPlayGame game, int resp) {
        if(!hasLogin()) {
            Log.d(TAG, "drawRespond fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_DRAW, new DrawData(mLoginUser.getId(), game.getId(), USER_RESP, resp));
    }

    public List<String> getMoves(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "getMoves fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return null;
        }
        RespGetMovesData data = (RespGetMovesData)sendSync(CMD_GET_MOVES, new GetMovesData(mLoginUser.getId(), game.getId()));
        if(data == null) {
            mErrorCode = ERROR_TIMEOUT;
            return null;
        }
        mErrorCode = data.errorCode;
        return data.moves;
    }

    public void ready(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "ready fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_READY, new ReadyData(mLoginUser.getId(), game.getId()));
    }

    public void gameOver(IPlayGame game) {
        if(!hasLogin()) {
            Log.d(TAG, "gameOver fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_GAME_OVER, new GameOverData(mLoginUser.getId(), game.getId()));
    }

    public void sendMessage(IRoom room, String msg) {
        if(!hasLogin()) {
            Log.d(TAG, "sendMessage fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_SEND_MESSAGE, new SendMessageData(mLoginUser.getId(), room.getId(), 0, 0, msg));
    }

    public void sendMessage(IPlayGame game, String msg) {
        if(!hasLogin()) {
            Log.d(TAG, "sendMessage fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_SEND_MESSAGE, new SendMessageData(mLoginUser.getId(), 0, game.getId(), 0, msg));
    }

    public void sendMessage(IUser toUser, String msg) {
        if(!hasLogin()) {
            Log.d(TAG, "sendMessage to user fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        sendSync(CMD_SEND_MESSAGE, new SendMessageData(mLoginUser.getId(), 0, 0, toUser.getId(), msg));
    }

    public Object getInfo(int id) {
        if(!hasLogin()) {
            Log.d(TAG, "getInfo fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return null;
        }
        RespGetInfoData data = (RespGetInfoData)sendSync(CMD_GET_INFO,
                new GetInfoData(mLoginUser.getId(), id));
        if(data == null) {
            mErrorCode = ERROR_TIMEOUT;
            return null;
        }
        mErrorCode = data.errorCode;
        return data.obj;
    }

    public IPlayGame getGameInfo(int gameId) {
        return (IPlayGame) getInfo(gameId);
    }

    public IUser getUserInfo(int userId) {
        return (IUser) getInfo(userId);
    }

    public void setNickName(String name) {
        if(!hasLogin()) {
            Log.d(TAG, "setNickName fail, not login!");
            mErrorCode = ERROR_NOT_LOGIN;
            return;
        }
        send(CMD_SET_NICKNAME, new SetNickNameData(mLoginUser.getId(), name));
    }
}
